module GraphUtil (
                  extractOneCycle,
                  extractOneCycle_gmap,
                  extractOneCycle_map,
                  findPathEdges,
                  reverseMap
                  ) where

-- ==================================================
-- GraphUtil
--
-- These are useful functions which span more than one
-- type of graph (GraphMap, Data.Graph, etc)
--
-- ==================================================

import Data.Maybe(isJust)

import Util(tailOrErr, initOrErr)
import ErrorUtil
import PPrint

import qualified GraphMap as G
import qualified GraphWrapper as GW

import qualified Data.Map as M

import System.IO.Unsafe

-- ===============

-- find one cyclic (non-repeating) path in the SCC
extractOneCycle :: (Ord nodeT, PPrint nodeT) =>
                   [(nodeT,nodeT)] -> [nodeT] -> [nodeT]
extractOneCycle edges cycle@(a:b:_) =
    let nodes = cycle
        g = unsafePerformIO (GW.makeGraph nodes edges)

        intErr s = internalError ("extractOneCyle: " ++ s)

        findPath x y =
            case (GW.findReachables g [x]) of
                [ps] -> case (lookup y ps) of
                            Just path -> reverse path
                            Nothing -> intErr ("lookup: " ++ ppReadable ps)
                x -> intErr ("reachables: " ++ ppReadable x)

        path_a_to_b = findPath a b
        path_b_to_a = findPath b a
        -- join the two paths, but with no duplicate node b in the middle
        cycle_path =
            path_a_to_b ++
            tailOrErr ("extractOneCycle: path_b_to_a does not contain b:"
                       ++ ppReadable path_b_to_a)
                      path_b_to_a
    in  cycle_path
extractOneCycle _ [a] = [a] -- a cycle from a node to itself
extractOneCycle _ [] =
    internalError ("extractOneCycle: cycle has no nodes")


-- extract a cycle given a GraphMap
extractOneCycle_gmap :: (Ord nodeT, PPrint nodeT) =>
                        G.GraphMap nodeT edgeT -> [nodeT] -> [nodeT]
extractOneCycle_gmap gmap cycle =
    let edges = [ (r, r') | r <- cycle, r' <- cycle, r' /= r,
                            isJust (G.lookup (r,r') gmap) ]
    in  extractOneCycle edges cycle


-- extract a cycle given a Map
extractOneCycle_map :: (Ord nodeT, PPrint nodeT) =>
                       M.Map nodeT [nodeT] -> [nodeT] -> [nodeT]
extractOneCycle_map m cycle =
    let edges = [ (r, r') | r <- cycle,
                            let rs = M.findWithDefault [] r m,
                            r' <- rs,
                            r' `elem` cycle ]
    in  extractOneCycle edges cycle


-- ===============

-- Given a list of nodes in a circular path (where the start and end
-- nodes in the list are the same), this returns a list of pairs of
-- nodes in the path along with the edge for that pair (from the GraphMap).
findPathEdges :: (Ord nodeT, PPrint nodeT) =>
                 G.GraphMap nodeT edgeT -> [nodeT] ->
                 [((nodeT, nodeT), edgeT)]
findPathEdges gmap path =
    let
        -- the path without the start node (which is same as the end)
        path_minus_start =
            tailOrErr
                ("findPathEdges: path_minus_start" ++ ppReadable path)
                path
        -- the path without the end node (which is same as the start)
        path_minus_end =
            initOrErr
                ("findPathEdges: path_minus_end: " ++ ppReadable path)
                path
        -- all the edges in the circular path
        path_pairs = zip path_minus_end path_minus_start
        -- lookup which shouldn't fail
        getEdge pair =
            case (G.lookup pair gmap) of
                Nothing -> internalError ("findPathEdges: lookup failed: " ++
                                          ppReadable pair)
                Just edge -> (pair,edge)
    in
        map getEdge path_pairs


-- ===============

-- takes a list of edges (of any kind) and produces a reverse map
reverseMap :: (Ord a) => M.Map a [a] -> M.Map a [a]
reverseMap m =
    let edges = M.toList m
        startEdge (e1,_) = (e1, [])
        reverseEdge (e1,es) = [(e2,[e1]) | e2 <- es]
        -- make sure that the map has "[]" for nodes with no ingoing edges
        -- XXX alternatively, users of this map could treat lookup failure
        -- XXX as meaning the empty list
        rev_edges = map startEdge edges ++
                    concatMap reverseEdge edges
    in M.fromListWith (++) rev_edges

-- ===============
